import Q  from  './vendor/q';
import u  from  './vendor/underscore';
import utils  from  './utils';
import events  from './events';
import kDefaultOptions  from './config';
import PutObjectTask  from './put_object_task';
import MultipartTask  from './multipart_task';
import StsTokenManager  from './sts_token_manager';
import NetworkInfo  from './network_info';
import Auth  from './bce-sdk-js/auth';
import BosClient  from './bce-sdk-js/bos_client';
import PolicyManager from './policy_manager';
import PostObjectTask from './post_object_task';
//获取文件管理器
const fileSystemManager = wx && wx.canIUse && wx.canIUse('getFileSystemManager') ?  wx.getFileSystemManager() : null;
/**
 * BCE BOS Uploader
 * @constructor
 * @param {Object} options 配置参数
 */
function Uploader(options) {


    var runtimeOptions = {};

    this.options = u.extend({}, kDefaultOptions, runtimeOptions, options);

    this.options.max_file_size = utils.parseSize(this.options.max_file_size);

    this.options.bos_multipart_min_size
        = utils.parseSize(this.options.bos_multipart_min_size);

    this.options.wx_read_file_max_size = utils.parseSize(this.options.wx_read_file_max_size);

    this.options.chunk_size = utils.parseSize(this.options.chunk_size);

    var credentials = this.options.bos_credentials;

    if (!credentials && this.options.bos_ak && this.options.bos_sk) {
        this.options.bos_credentials = {
            ak: this.options.bos_ak,
            sk: this.options.bos_sk
        };
    }

    /**
     * @type {BosClient}
     */
    this.client = new BosClient({
        endpoint: utils.normalizeEndpoint(this.options.bos_endpoint),
        credentials: this.options.bos_credentials,
        sessionToken: this.options.uptoken
    });

    /**
     * 需要等待上传的文件列表，每次上传的时候，从这里面删除
     * 成功或者失败都不会再放回去了
     *
     * @type {Array.<File>}
     */
    this._files = [];

    /**
     * 正在上传的文件列表.
     *
     * @type {Object.<string, File>}
     */
    this._uploadingFiles = {};

    /**
     * 是否被中断了，比如 this.stop
     * @type {boolean}
     */
    this._abort = false;

    /**
     * 是否处于上传的过程中，也就是正在处理 this._files 队列的内容.
     * @type {boolean}
     */
    this._working = false;

    /**
     * 是否支持xhr2
     * @type {boolean}
     */
    this._xhr2Supported = true;

    this._networkInfo = new NetworkInfo();

    this._init();
    
    this._policyManager = null;


}

Uploader.prototype._getCustomizedSignature = function (uptokenUrl) {
    var options = this.options;
    var timeout = options.uptoken_timeout || options.uptoken_jsonp_timeout;
    return function (_, httpMethod, path, params, headers) {
        if (u.isArray(options.auth_stripped_headers)) {
            headers = u.omit(headers, options.auth_stripped_headers);
        }
        var deferred = Q.defer();
        wx.request({
          url : uptokenUrl,
          data:{
            httpMethod: httpMethod,
            path: path,
            queries: JSON.stringify(params || {}),
            headers: JSON.stringify(headers || {})
          },
          dataType:'json',
          success: function (payload){
            if (payload.statusCode === 200 && payload.signature) {
              deferred.resolve(payload.signature, payload.xbceDate);
            }
            else {
              deferred.reject(new Error('createSignature failed, statusCode = ' + payload.statusCode));
            }
          },
          fail:function(){
             deferred.reject(new Error('Get authorization timeout (' + timeout + 'ms).'));
          }
        });
        return deferred.promise;
    };
};

/**
 * 调用 this.options.init 里面配置的方法
 * @param {string} methodName 方法名称
 * @param {Array.<*>} args 调用时候的参数.
 * @param {boolean=} throwErrors 如果发生异常的时候，是否需要抛出来
 * @return {*} 事件的返回值.
 */
Uploader.prototype._invoke = function (methodName, args, throwErrors) {
    var init = this.options.init || this.options.Init;
    if (!init) {
        return;
    }
    var method = init[methodName];
    if (typeof method !== 'function') {
        return;
    }
    try {
        var up = null;
        args = args == null ? [up] : [up].concat(args);
        return method.apply(null, args);
    }
    catch (ex) {
        utils.dumpError(ex);
        if (throwErrors === true) {
            return Q.reject(ex);
        }
    }
};

/**
 * 初始化控件.
 */
Uploader.prototype._init = function () {
    var options = this.options;
    var accept = options.accept;
    var self = this;
	  //初始化事件
	self._initEvents();
    self._invoke(events.kPostInit);
    var promise = options.bos_credentials
        ? Q.resolve()
        : self.refreshStsToken();
     promise.then(function () {
          if (options.bos_credentials) {
              self.client.createSignature = function (_, httpMethod, path, params, headers) {
                  var credentials = _ || this.config.credentials;
                  return Q.fcall(function () {
                      var auth = new Auth(credentials.ak, credentials.sk);
                      return auth.generateAuthorization(httpMethod, path, params, headers);
                  });
              };
          }
          else if (options.uptoken_url && options.get_new_uptoken === true) {
              // 服务端动态签名的方式
              self.client.createSignature = self._getCustomizedSignature(options.uptoken_url);
          }
          if (self._xhr2Supported) {
              // 对于不支持 xhr2 的情况，会在 onready 的时候去触发事件
              self._initEvents();
              self._invoke(events.kPostInit);
          }

          self._policyManager = new PolicyManager(options);

    }).catch(function (error) {
        self._invoke(events.kError, [error]);
    });
};

Uploader.prototype._initEvents = function () {

    this.client.on('progress', u.bind(this._onUploadProgress, this));

    // XXX 必须绑定 error 的处理函数，否则会 throw new Error
    this.client.on('error', u.bind(this._onError, this));

};

//判断文件大小是否超过限制
Uploader.prototype._filterFiles = function (candidates) {
    var self = this;
    // 如果 maxFileSize === 0 就说明不限制大小
    var maxFileSize = this.options.max_file_size;
    var files = u.filter(candidates, function (file) {
        if (maxFileSize > 0 && file.size > maxFileSize) {
            self._invoke(events.kFileFiltered, [file]);
            return false;
        }
        return true;
    });
    return this._invoke(events.kFilesFilter, [files]) || files;
};

//添加文件的事件
Uploader.prototype._onFilesAdded = function (tempFiles) {
	let files = [];
	if(tempFiles && u.isArray(tempFiles)){
		for(let i = 0 ;i < tempFiles.length; i ++){
			let file = {
				name : tempFiles[i].path,
				size : tempFiles[i].size
			},
			type = utils.guessContentType(file);
			file.type = type;
			files.push(file);
		}
	}
    files = this._filterFiles(files);
    if (u.isArray(files) && files.length) {
        this.addFiles(files);
    }

    if (this.options.auto_start) {
        this.start();
    }
};

Uploader.prototype._onError = function (e) {

};

/**
 * 处理上传进度的回掉函数.
 * 1. 这里要区分文件的上传还是分片的上传，分片的上传是通过 partNumber 和 uploadId 的组合来判断的
 * 2. IE6,7,8,9下面，是不需要考虑的，因为不会触发这个事件，而是直接在 _sendPostRequest 触发 kUploadProgress 了
 * 3. 其它情况下，我们判断一下 Request Body 的类型是否是 Blob，从而避免对于其它类型的请求，触发 kUploadProgress
 *    例如：HEAD，GET，POST(InitMultipart) 的时候，是没必要触发 kUploadProgress 的
 * @param {Object} e  Progress Event 对象.
 * @param {Object} httpContext sendHTTPRequest 的参数
 */
Uploader.prototype._onUploadProgress = function (e, httpContext) {
    var args = httpContext.args;
    var file = args.body;

    if (!utils.isBlob(file)) {
        return;
    }

    var progress = e.lengthComputable
        ? e.loaded / e.total
        : 0;
    var delta = e.loaded - file._previousLoaded;
    this._networkInfo.loadedBytes += delta;
    this._invoke(events.kNetworkSpeed, this._networkInfo.dump());
    file._previousLoaded = e.loaded;

    var eventType = events.kUploadProgress;
    if (args.params.partNumber && args.params.uploadId) {
        // IE6,7,8,9下面不会有partNumber和uploadId
        // 此时的 file 是 slice 的结果，可能没有自定义的属性
        // 比如 demo 里面的 __id, __mediaId 之类的
        eventType = events.kUploadPartProgress;
        this._invoke(eventType, [file, progress, e]);
        // 然后需要从 file 获取原始的文件（因为 file 此时是一个分片）
        // 之后再触发一次 events.kUploadProgress 的事件
        var uuid = file._parentUUID;
        var originalFile = this._uploadingFiles[uuid];
        var originalFileProgress = 0;
        if (originalFile) {
            originalFile._previousLoaded += delta;
            originalFileProgress = Math.min(originalFile._previousLoaded / originalFile.size, 1);
            this._invoke(events.kUploadProgress, [originalFile, originalFileProgress, null]);
        }
    }
    else {
        this._invoke(eventType, [file, progress, e]);
    }
};

//加入文件
Uploader.prototype.addFiles = function (files) {
    function buildAbortHandler(item, self) {
        return function () {
            item._aborted = true;
            self._invoke(events.kAborted, [null, item]);
        };
    }
    var totalBytes = 0;
    for (var i = 0; i < files.length; i++) {
        var item = files[i];
        // 这里是 abort 的默认实现，开始上传的时候，会改成另外的一种实现方式
        // 默认的实现是为了支持在没有开始上传之前，也可以取消上传的需求
        item.abort = buildAbortHandler(item, this);
        // 内部的 uuid，外部也可以使用，比如 remove(item.uuid) / remove(item)
        item.uuid = utils.uuid();
        totalBytes += item.size;
    }
    this._networkInfo.totalBytes += totalBytes;
    this._files.push.apply(this._files, files);
    this._invoke(events.kFilesAdded, [files]);
};

Uploader.prototype.addFile = function (file) {
    this.addFiles([file]);
};
Uploader.prototype.remove = function (item) {
    if (typeof item === 'string') {
        item = this._uploadingFiles[item] || u.find(this._files, function (file) {
            return file.uuid === item;
        });
    }
    if (item && typeof item.abort === 'function') {
        item.abort();
    }
};
Uploader.prototype.start = function () {
    var self = this;
    var options = self.options;
    if (this._working) {
        return;
    }
    if (this._files.length) {
        this._working = true;
        this._abort = false;
        this._networkInfo.reset();
        var taskParallel = this.options.bos_task_parallel;
        // 这里没有使用 async.eachLimit 的原因是 this._files 可能会被动态的修改
        utils.eachLimit(this._files, taskParallel,
		function (file, callback) {
            file._previousLoaded = 0;
            /**
             * 如果文件的大小小于限制
             */
            if(file.size > options.wx_read_file_max_size || !fileSystemManager){
                self._isPostObject = true;
                self._uploadNext(file)
                .then(function () {
                    // fulfillment
                    delete self._uploadingFiles[file.uuid];
                    callback(null, file);
                })
                .catch(function () {
                    // rejection
                    delete self._uploadingFiles[file.uuid];
                    callback(null, file);
                });
            }else{
                //微信小程序读取文件
                fileSystemManager.readFile({
                    filePath : file.name,
                    success : function(buffer){
                        self._isPostObject = false;
                        file.blob = buffer.data;
                        self._uploadNext(file)
                        .then(function () {
                            // fulfillment
                            delete self._uploadingFiles[file.uuid];
                            callback(null, file);
                        })
                        .catch(function () {
                            // rejection
                            delete self._uploadingFiles[file.uuid];
                            callback(null, file);
                        });
                    },
                    fail : function(error){
                        self._isPostObject = true;
                        //读取文件失败，不能上传
                        self._uploadNext(file)
                        .then(function () {
                            // fulfillment
                            delete self._uploadingFiles[file.uuid];
                            callback(null, file);
                        })
                        .catch(function () {
                            // rejection
                            delete self._uploadingFiles[file.uuid];
                            callback(null, file);
                        });
                    }
                });
            }
		
		},
		function (error) {
			self._working = false;
			self._files.length = 0;
			self._networkInfo.totalBytes = 0;
			self._invoke(events.kUploadComplete);
		});
    }
};

Uploader.prototype.stop = function () {
    this._abort = true;
    this._working = false;
};

/**
 * 动态设置 Uploader 的某些参数，当前只支持动态的修改
 * bos_credentials, uptoken, bos_bucket, bos_endpoint
 * bos_ak, bos_sk, init
 * @param {Object} options 用户动态设置的参数（只支持部分）
 */
Uploader.prototype.setOptions = function (options) {
    var supportedOptions = u.pick(options, 'bos_credentials',
        'bos_ak', 'bos_sk', 'uptoken', 'bos_bucket', 'bos_endpoint');
    this.options = u.extend(this.options, supportedOptions);
    var config = this.client && this.client.config;
    if (config) {
        var credentials = null;

        if (options.bos_credentials) {
            credentials = options.bos_credentials;
        }
        else if (options.bos_ak && options.bos_sk) {
            credentials = {
                ak: options.bos_ak,
                sk: options.bos_sk
            };
        }
        if (credentials) {
            this.options.bos_credentials = credentials;
            config.credentials = credentials;
        }
        if (options.uptoken) {
            config.sessionToken = options.uptoken;
        }
        if (options.bos_endpoint) {
            config.endpoint = utils.normalizeEndpoint(options.bos_endpoint);
        }
    }
    if(options.init){
        var tmp = this.options.init;
        if(typeof Object.assign === 'function'){
            tmp = Object.assign({},this.options.init,options.init);
        }else{
            tmp = u.extend({},this.options.init,options.init);
        }
        this.options.init = tmp ;
    }
};
/**
 * 有的用户希望主动更新 sts token，避免过期的问题
 * @param {string=} bucket The bucket name.
 * @return {Promise}
 */
Uploader.prototype.refreshStsToken = function (bucket) {
    var self = this;
    var options = self.options;
    var bos_bucket = bucket || options.bos_bucket;
    var stsMode = true // self._xhr2Supported
        && bos_bucket
        && options.uptoken_url
        && options.get_new_uptoken === false;
    if (stsMode) {
        var stm = new StsTokenManager(options);
        return stm.get(bos_bucket).then(function (payload) {
            return self.setOptions({
                bos_ak: payload.AccessKeyId,
                bos_sk: payload.SecretAccessKey,
                uptoken: payload.SessionToken
            });
        });
    }
    return Q.resolve();
};

Uploader.prototype._uploadNext = function (file) {
    if (this._abort) {
        this._working = false;
        return Q.resolve();
    }
    if (file._aborted === true) {
        return Q.resolve();
    }
    var throwErrors = true;
    var returnValue = this._invoke(events.kBeforeUpload, [file], throwErrors);

    if (returnValue === false) {
        return Q.resolve();
    }

    var self = this;
    return Q.resolve(returnValue)
        .then(function () {
            return self._uploadNextImpl(file);
        })
        .catch(function (error) {
            self._invoke(events.kError, [error, file]);
        });
};

Uploader.prototype._uploadNextImpl = function (file) {
    var self = this;
    var options = this.options;
    var object = file.name;
    var throwErrors = true;
    var defaultTaskOptions = u.pick(options,
        'flash_swf_url', 'max_retries', 'chunk_size', 'retry_interval',
        'bos_multipart_parallel',
        'bos_multipart_auto_continue',
        'bos_multipart_local_key_generator'
    );
    return Q.all([
        this._invoke(events.kKey, [file,self._isPostObject], throwErrors),
        this._invoke(events.kObjectMetas, [file])
    ]).then(function (array) {
        // options.bos_bucket 可能会被 kKey 事件动态的改变
        var bucket = options.bos_bucket;
        var result = array[0];
        var objectMetas = array[1];
        var multipart = 'auto';
        if (u.isString(result)) {
            object = result;
        }
        else if (u.isObject(result)) {
            bucket = result.bucket || bucket;
            object = result.key || object;
            multipart = result.multipart || multipart;
        }
        var client = self.client;
        var eventDispatcher = self;
        var taskOptions = u.extend(defaultTaskOptions, {
            file: file,
            bucket: bucket,
            object: object,
            metas: objectMetas
        });
        var task = null;
        if(self._isPostObject){
             task = new PostObjectTask(client,eventDispatcher,taskOptions);
             task.setPolicyManager(self._policyManager);   
        }
        else if (multipart === 'auto'
            && self._xhr2Supported
            && file.size > options.bos_multipart_min_size) {
            task = new MultipartTask(client,eventDispatcher,taskOptions);    
        }else{
            task = new PutObjectTask(client,eventDispatcher,taskOptions);
        }
        self._uploadingFiles[file.uuid] = file;
        file.abort = function () {
            file._aborted = true;
            return task.abort();
        };
        task.setNetworkInfo(self._networkInfo);
        return task.start();
    });
};
Uploader.prototype.dispatchEvent = function (eventName, eventArguments, throwErrors) {
    if (eventName === events.kAborted
        && eventArguments
        && eventArguments[1]) {
        var file = eventArguments[1];
        if (file.size > 0) {
            var loadedSize = file._previousLoaded || 0;
            this._networkInfo.totalBytes -= (file.size - loadedSize);
            this._invoke(events.kNetworkSpeed, this._networkInfo.dump());
        }
    }
    return this._invoke(eventName, eventArguments, throwErrors);
};

//外部调用的添加文件的方法
Uploader.prototype.addFilesOuter = function(tempFiles){
	this._onFilesAdded(tempFiles);
}
export default  Uploader;
